﻿namespace DomainServices.Tools
{
    using System;
    using System.CodeDom;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.ComponentModel.DataAnnotations;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Runtime.Serialization;
    using System.ServiceModel.DomainServices;
    using System.ServiceModel.DomainServices.Server;

    internal abstract class DataContractProxyGenerator : ProxyGenerator
    {
        private bool _isRoundtripType;
        private IDictionary<System.Type, CodeTypeDeclaration> _typeMapping;

        protected DataContractProxyGenerator(CodeDomClientCodeGenerator proxyGenerator, System.Type type, IDictionary<System.Type, CodeTypeDeclaration> typeMapping) : base(proxyGenerator)
        {
            this.Type = type;
            this._typeMapping = typeMapping;
            this.NotificationMethodGen = new NotificationMethodGenerator(proxyGenerator);
            this._isRoundtripType = type.Attributes()[typeof(RoundtripOriginalAttribute)] != null;
        }

        protected abstract void AddBaseTypes(CodeNamespace ns);
        protected virtual bool CanGenerateProperty(PropertyDescriptor propertyDescriptor)
        {
            System.Type propertyType = propertyDescriptor.PropertyType;
            if (!System.ServiceModel.DomainServices.SerializationUtility.IsSerializableDataMember(propertyDescriptor))
            {
                return false;
            }
            string errorMessage = null;
            System.Type nonNullableType = System.ServiceModel.DomainServices.TypeUtility.GetNonNullableType(propertyType);
            if (nonNullableType.IsEnum && !base.ClientProxyGenerator.CanExposeEnumType(nonNullableType, out errorMessage))
            {
                base.ClientProxyGenerator.LogWarning(string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.ClientCodeGen_Property_Enum_Error, new object[] { this.Type, propertyDescriptor.Name, nonNullableType.FullName, errorMessage }));
                return false;
            }
            return true;
        }

        protected virtual bool CanGeneratePropertyIfPolymorphic(PropertyDescriptor pd)
        {
            return true;
        }

        private IEnumerable<Attribute> FilterTypeAttributes(AttributeCollection typeAttributes)
        {
            List<Attribute> filteredAttributes = new List<Attribute>();
            IEnumerable<Attribute> source = from a in typeAttributes.Cast<Attribute>()
                where a.GetType() == typeof(DefaultMemberAttribute)
                select a;
            if (source.Any<Attribute>())
            {
                HashSet<string> set = new HashSet<string>(from p in TypeDescriptor.GetProperties(this.Type).Cast<PropertyDescriptor>() select p.Name, StringComparer.Ordinal);
                foreach (DefaultMemberAttribute attribute in source)
                {
                    if (!set.Contains(attribute.MemberName))
                    {
                        filteredAttributes.Add(attribute);
                    }
                }
            }
            return (from a in typeAttributes.Cast<Attribute>()
                where ((a.GetType() != typeof(DataContractAttribute)) && (a.GetType() != typeof(KnownTypeAttribute))) && !filteredAttributes.Contains(a)
                select a);
        }

        public override void Generate()
        {
            System.Func<System.Type, string> func = null;
            CodeNamespace orGenNamespace = base.ClientProxyGenerator.GetOrGenNamespace(this.Type);
            if (orGenNamespace != null)
            {
                this.ProxyClass = CodeGenUtilities.CreateTypeDeclaration(this.Type);
                this.ProxyClass.IsPartial = true;
                this.ProxyClass.TypeAttributes = TypeAttributes.Public;
                bool isAbstract = this.Type.IsAbstract;
                if (isAbstract)
                {
                    CodeTypeDeclaration proxyClass = this.ProxyClass;
                    proxyClass.TypeAttributes |= TypeAttributes.Abstract;
                }
                IEnumerable<System.Type> derivedTypes = this.GetDerivedTypes();
                if (!isAbstract && !derivedTypes.Any<System.Type>())
                {
                    CodeTypeDeclaration declaration3 = this.ProxyClass;
                    declaration3.TypeAttributes |= TypeAttributes.Sealed;
                }
                this.AddBaseTypes(orGenNamespace);
                orGenNamespace.Types.Add(this.ProxyClass);
                AttributeCollection typeAttributes = this.Type.Attributes();
                string summaryComment = this.GetSummaryComment();
                this.ProxyClass.Comments.AddRange(CodeGenUtilities.GenerateSummaryCodeComment(summaryComment, base.ClientProxyGenerator.IsCSharp));
                CodeConstructor constructor = new CodeConstructor {
                    Attributes = isAbstract ? MemberAttributes.Family : MemberAttributes.Public
                };
                summaryComment = string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.CodeGen_Default_Constructor_Summary_Comments, new object[] { this.Type.Name });
                constructor.Comments.AddRange(CodeGenUtilities.GenerateSummaryCodeComment(summaryComment, base.ClientProxyGenerator.IsCSharp));
                constructor.Statements.Add(this.NotificationMethodGen.OnCreatedMethodInvokeExpression);
                this.ProxyClass.Members.Add(constructor);
                if (!this.IsDerivedType)
                {
                    if (func == null)
                    {
                        func = delegate (System.Type t) {
                            if (!base.ClientProxyGenerator.ClientProxyCodeGenerationOptions.UseFullTypeNames)
                            {
                                return t.Name;
                            }
                            return t.FullName;
                        };
                    }
                    foreach (System.Type type in Enumerable.OrderBy<System.Type, string>(derivedTypes, func))
                    {
                        CodeAttributeDeclaration declaration = CodeGenUtilities.CreateAttributeDeclaration(typeof(KnownTypeAttribute), base.ClientProxyGenerator, this.ProxyClass);
                        declaration.Arguments.Add(new CodeAttributeArgument(new CodeTypeOfExpression(CodeGenUtilities.GetTypeReference(type, base.ClientProxyGenerator, this.ProxyClass))));
                        this.ProxyClass.CustomAttributes.Add(declaration);
                    }
                }
                this.ValidateTypeAttributes(typeAttributes);
                CodeAttributeDeclaration declaration2 = CodeGenUtilities.CreateDataContractAttributeDeclaration(this.Type, base.ClientProxyGenerator, this.ProxyClass);
                this.ProxyClass.CustomAttributes.Add(declaration2);
                CustomAttributeGenerator.GenerateCustomAttributes(base.ClientProxyGenerator, this.ProxyClass, ex => string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.ClientCodeGen_Attribute_ThrewException_CodeType, new object[] { ex.Message, this.ProxyClass.Name, ex.InnerException.Message }), this.FilterTypeAttributes(typeAttributes), this.ProxyClass.CustomAttributes, this.ProxyClass.Comments);
                this.GenerateProperties();
                this.GenerateAdditionalMembers();
                this._typeMapping[this.Type] = this.ProxyClass;
            }
        }

        protected virtual void GenerateAdditionalMembers()
        {
            this.ProxyClass.Members.AddRange(this.NotificationMethodGen.PartialMethodsSnippetBlock);
        }

        protected virtual bool GenerateNonSerializableProperty(PropertyDescriptor pd)
        {
            return false;
        }

        private void GenerateProperties()
        {
            foreach (PropertyDescriptor descriptor in from p in TypeDescriptor.GetProperties(this.Type).Cast<PropertyDescriptor>()
                orderby p.Name
                select p)
            {
                if (!this.ShouldDeclareProperty(descriptor))
                {
                    continue;
                }
                if (this.CanGenerateProperty(descriptor))
                {
                    if (this.CanGeneratePropertyIfPolymorphic(descriptor) && !this.GenerateNonSerializableProperty(descriptor))
                    {
                        System.Type type = CodeGenUtilities.TranslateType(descriptor.PropertyType);
                        List<System.Type> list = new List<System.Type>();
                        bool flag = true;
                        if (System.ServiceModel.DomainServices.TypeUtility.IsPredefinedDictionaryType(type))
                        {
                            list.AddRange(CodeGenUtilities.GetDictionaryGenericArgumentTypes(type));
                        }
                        else
                        {
                            list.Add(System.ServiceModel.DomainServices.TypeUtility.GetElementType(type));
                        }
                        foreach (System.Type type2 in list)
                        {
                            System.Type nonNullableType = System.ServiceModel.DomainServices.TypeUtility.GetNonNullableType(type2);
                            if (nonNullableType.IsEnum)
                            {
                                base.ClientProxyGenerator.RegisterUseOfEnumType(nonNullableType);
                            }
                            else if (!this.ComplexTypes.Contains<System.Type>(type2) && ((base.ClientProxyGenerator.GetTypeShareKind(nonNullableType) & CodeMemberShareKind.Shared) == CodeMemberShareKind.Unknown))
                            {
                                base.ClientProxyGenerator.LogWarning(string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.ClientCodeGen_PropertyType_Not_Shared, new object[] { descriptor.Name, this.Type.FullName, type2.FullName, base.ClientProxyGenerator.ClientProjectName }));
                                flag = false;
                            }
                        }
                        if (flag)
                        {
                            CodeTypeReference reference = CodeGenUtilities.GetTypeReference(type, base.ClientProxyGenerator, this.ProxyClass);
                            this.NotificationMethodGen.AddMethodFor(descriptor.Name + "Changing", new CodeParameterDeclarationExpression(reference, "value"), null);
                            this.NotificationMethodGen.AddMethodFor(descriptor.Name + "Changed", null);
                            this.GenerateProperty(descriptor);
                        }
                    }
                    continue;
                }
                this.OnPropertySkipped(descriptor);
            }
        }

        protected virtual void GenerateProperty(PropertyDescriptor propertyDescriptor)
        {
            string name = propertyDescriptor.Name;
            System.Type type = CodeGenUtilities.TranslateType(propertyDescriptor.PropertyType);
            CodeTypeReference reference = CodeGenUtilities.GetTypeReference(type, base.ClientProxyGenerator, this.ProxyClass);
            CodeMemberProperty property = new CodeMemberProperty {
                Name = name,
                Type = reference,
                Attributes = MemberAttributes.Public | MemberAttributes.Final
            };
            List<Attribute> source = propertyDescriptor.ExplicitAttributes().Cast<Attribute>().ToList<Attribute>();
            string comment = string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.CodeGen_Entity_Property_Summary_Comment, new object[] { name });
            property.Comments.AddRange(CodeGenUtilities.GenerateSummaryCodeComment(comment, base.ClientProxyGenerator.IsCSharp));
            if (!source.OfType<DataMemberAttribute>().Any<DataMemberAttribute>())
            {
                CodeAttributeDeclaration declaration = CodeGenUtilities.CreateAttributeDeclaration(typeof(DataMemberAttribute), base.ClientProxyGenerator, this.ProxyClass);
                property.CustomAttributes.Add(declaration);
            }
            ReadOnlyAttribute attribute = source.OfType<ReadOnlyAttribute>().SingleOrDefault<ReadOnlyAttribute>();
            if ((attribute != null) && !source.OfType<EditableAttribute>().Any<EditableAttribute>())
            {
                source.Add(new EditableAttribute(!attribute.IsReadOnly));
            }
            if (this._isRoundtripType)
            {
                source.RemoveAll(attr => attr.GetType() == typeof(RoundtripOriginalAttribute));
            }
            if (System.ServiceModel.DomainServices.TypeUtility.IsSupportedComplexType(type) && !source.OfType<DisplayAttribute>().Any<DisplayAttribute>())
            {
                CodeAttributeDeclaration declaration2 = CodeGenUtilities.CreateDisplayAttributeDeclaration(base.ClientProxyGenerator, this.ProxyClass);
                property.CustomAttributes.Add(declaration2);
            }
            CustomAttributeGenerator.GenerateCustomAttributes(base.ClientProxyGenerator, this.ProxyClass, ex => string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.ClientCodeGen_Attribute_ThrewException_CodeTypeMember, new object[] { ex.Message, property.Name, this.ProxyClass.Name, ex.InnerException.Message }), source.Cast<Attribute>(), property.CustomAttributes, property.Comments);
            string str3 = CodeGenUtilities.MakeCompliantFieldName(name);
            CodeMemberField field = new CodeMemberField(reference, str3);
            this.ProxyClass.Members.Add(field);
            CodeFieldReferenceExpression expression = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), str3);
            CodePropertySetValueReferenceExpression right = new CodePropertySetValueReferenceExpression();
            property.GetStatements.Add(new CodeMethodReturnStatement(expression));
            List<CodeStatement> list2 = new List<CodeStatement> {
                this.NotificationMethodGen.GetMethodInvokeExpressionStatementFor(name + "Changing")
            };
            bool flag = this.IsPropertyReadOnly(propertyDescriptor);
            if (!flag)
            {
                list2.Add(new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "RaiseDataMemberChanging", new CodeExpression[] { new CodePrimitiveExpression(propertyDescriptor.Name) })));
            }
            CodeStatement item = GeneratePropertySetterValidation(propertyDescriptor.Name);
            list2.Add(item);
            list2.Add(new CodeAssignStatement(expression, right));
            if (!flag)
            {
                list2.Add(new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "RaiseDataMemberChanged", new CodeExpression[] { new CodePrimitiveExpression(propertyDescriptor.Name) })));
            }
            else
            {
                list2.Add(new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "RaisePropertyChanged", new CodeExpression[] { new CodePrimitiveExpression(propertyDescriptor.Name) })));
            }
            list2.Add(this.NotificationMethodGen.GetMethodInvokeExpressionStatementFor(name + "Changed"));
            CodeConditionStatement statement2 = new CodeConditionStatement(CodeGenUtilities.MakeNotEqual(type, expression, right, base.ClientProxyGenerator.IsCSharp), list2.ToArray<CodeStatement>());
            property.SetStatements.Add(statement2);
            this.ProxyClass.Members.Add(property);
        }

        protected static CodeStatement GeneratePropertySetterValidation(string propertyName)
        {
            return new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "ValidateProperty", new CodeExpression[] { new CodePrimitiveExpression(propertyName), new CodePropertySetValueReferenceExpression() }));
        }

        protected abstract IEnumerable<System.Type> GetDerivedTypes();
        protected abstract string GetSummaryComment();
        private bool IsExcluded(PropertyDescriptor pd, AttributeCollection propertyAttributes)
        {
            bool flag = propertyAttributes[typeof(ExcludeAttribute)] != null;
            if (flag && (propertyAttributes[typeof(IncludeAttribute)] != null))
            {
                base.ClientProxyGenerator.LogWarning(string.Format(CultureInfo.CurrentCulture, DomainServices.Tools.Resource.ClientCodeGen_Cannot_Have_Include_And_Exclude, new object[] { pd.Name, this.Type }));
            }
            return flag;
        }

        protected bool IsPropertyReadOnly(PropertyDescriptor property)
        {
            ReadOnlyAttribute attribute = property.Attributes[typeof(ReadOnlyAttribute)] as ReadOnlyAttribute;
            if ((attribute != null) && attribute.IsReadOnly)
            {
                return true;
            }
            EditableAttribute attribute2 = property.Attributes[typeof(EditableAttribute)] as EditableAttribute;
            return ((attribute2 != null) && !attribute2.AllowEdit);
        }

        protected virtual bool IsPropertyShared(PropertyDescriptor pd)
        {
            return ((base.ClientProxyGenerator.GetPropertyShareKind(this.Type, pd.Name) & CodeMemberShareKind.Shared) != CodeMemberShareKind.Unknown);
        }

        protected virtual void OnPropertySkipped(PropertyDescriptor pd)
        {
        }

        protected virtual bool ShouldDeclareProperty(PropertyDescriptor pd)
        {
            AttributeCollection propertyAttributes = pd.ExplicitAttributes();
            if (this.IsExcluded(pd, propertyAttributes))
            {
                return false;
            }
            if (this.IsPropertyShared(pd))
            {
                return false;
            }
            return true;
        }

        protected virtual void ValidateTypeAttributes(AttributeCollection typeAttributes)
        {
        }

        protected abstract IEnumerable<System.Type> ComplexTypes { get; }

        protected abstract bool IsDerivedType { get; }

        protected NotificationMethodGenerator NotificationMethodGen { get; private set; }

        protected CodeTypeDeclaration ProxyClass { get; private set; }

        protected System.Type Type { get; private set; }
    }
}

